/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package MIPS;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

/**
 *
 * @author Keiner - jorgue
 */
public class MIPS {
    
    private final int cantHilos = 5;
    private pizarra[] zonaCritica;// = new ArrayList<panelHilo>(cantPaneles);
    private Thread[] hilos;// = new ArrayList<panelHilo>(cantPaneles);
    private Etapa[] etapas;
    private Semaphore[] organizador;
    private Semaphore principal;
    private final int INICIO_INSTRUCCIONES = 0;
    private final int FIN_INSTRUCCIONES = 79;
    private final int INICIO_DATOS = 80;
    private final int FIN_DATOS = 256;
    private volatile int[][] memoriaDatos = new int[350][4];
    private volatile int[][] memoriaInstrucciones = new int[160][4];
    private static final int IF = 0;
    private static final int ID = 1;
    private static final int EX = 2;
    private static final int MEM = 3;
    private static final int WB = 4;
    private static int inicioDatos = 640;
    Cache cacheInstrucciones;
    Cache cacheDatos;
    public int[] registros;
    private ArrayList<Programa> colaCircular;
    private int programaActual = 0;
    int quantumActual = 0;
    int quantum = 0;
    
    public MIPS(int retrasoFallo, int quantum) {
        colaCircular = new ArrayList<Programa>();  //Creación de lista circular que guardará los contextos de cada programa
        registros = new int[32];                   //Juego de Registros para cada programa
        zonaCritica = new pizarra[cantHilos - 1];  //Zona crítica es el área compartida entre etapas
        hilos = new Thread[cantHilos];             //Hilos para cada etapa
        etapas = new Etapa[cantHilos];             //Se asigna a cada etapa un hilo
        organizador = new Semaphore[cantHilos];    //Semáforo para cada etapa
        principal = new Semaphore(cantHilos);      //Semáforo para el Hilo Principal
        cacheInstrucciones = new Cache(memoriaInstrucciones, retrasoFallo, 0);  //Estructura de datos para la caché de instrucciones
        cacheDatos = new Cache(memoriaDatos, retrasoFallo, inicioDatos);        //Estructura de datos para la caché de datos
        this.quantum = 3;//quantum;
        
    }
    
    public void correrMIPS() {
        inicializarMemorias();
        cargarArchivos();
        //imprimirMemoria(memoriaInstrucciones);
        creaHilos();
        correrHilos();
    }
    
    /// Se encarga de crear los hilos y asisgnar las zonas críticas a cada par de etapas
    private void creaHilos() {
        for (int i = 0; i < organizador.length; i++) {
            organizador[i] = new Semaphore(0, true);
        }
        for (int i = 0; i < zonaCritica.length; i++) {
            zonaCritica[i] = new pizarra();
        }
        etapas[IF] = new EtapaIF(null, zonaCritica[0], organizador[0], principal, cacheInstrucciones);
        etapas[ID] = new EtapaID(zonaCritica[0], zonaCritica[1], organizador[1], principal, registros);
        etapas[EX] = new EtapaEX(zonaCritica[1], zonaCritica[2], organizador[2], principal);
        etapas[MEM] = new EtapaMEM(zonaCritica[2], zonaCritica[3], organizador[3], principal, cacheDatos);
        etapas[WB] = new EtapaWB(zonaCritica[3], null, organizador[4], principal, registros);
        for (int i = 0; i < etapas.length; i++) {
            hilos[i] = new Thread(etapas[i]);
        }
        
    }
    
    //Corre los hilos. Es donde ocurre la sincronización
    private void correrHilos() {
  
        cargarProgramaActual();
        //aqui los pone a correr
        for (int i = 0; i < hilos.length; i++) {
            hilos[i].start();
        }
        for (int i = 0; i < 1000; i++) {
            try {
                principal.acquire(cantHilos);
                System.out.println("Actualizando zonas criticas en ciclo " + i);
                
                for (int j = 0; j < 4; j++) {
                    zonaCritica[j].setCopiado(false);
                }
                if (quantumActual == quantum) {
                    ((EtapaIF)etapas[IF]).setCambio(true);
                }
                quantumActual++;
                
                if(etapas[WB].getCambio() == true) {  //Esto implica cambio de contexto
                    guardarContexto();
                    for (int j = 0; j < zonaCritica.length; j++) { //refresca todos los valores después del cambio
                        zonaCritica[j].setCambio(false);
                        zonaCritica[j].setIR(null);
                        zonaCritica[j].setPC(-1);
                        zonaCritica[j].setRB(-1);
                        zonaCritica[j].setRA(-1);
                        zonaCritica[j].setRL(-1);
                        zonaCritica[j].setImm(-1);
                    }
                    ((EtapaIF)etapas[IF]).setCambio(false); 
                    ((EtapaMEM)etapas[MEM]).setPCListo(-2);
                    ((EtapaWB)etapas[WB]).setPCListo(-2);   
                    
                    System.out.println("Cambio de Contexto");
                    programaActual++;
                    if (programaActual == colaCircular.size()) {
                        programaActual = 0;
                    }
                    cargarProgramaActual();  //carga el programa actual en el pipeline 
                    quantumActual = 0;
                }
                //Actualización de set de Registros después de cada WB
                ((EtapaID)etapas[ID]).setRegistros(((EtapaWB)etapas[WB]).getJuegoRegistros());
                //manejo de retrasos por fallo de caché de datos en la etapa MEM
                if(etapas[MEM].estaAtrasado() == true){
                    etapas[EX].setAtrasado(true);
                    etapas[ID].setAtrasado(true);
                    etapas[IF].setAtrasado(true);
                }else{
                    etapas[EX].setAtrasado(false);
                    etapas[ID].setAtrasado(false);
                    etapas[IF].setAtrasado(false);
                }
                JOptionPane.showConfirmDialog(null, "cambio de ciclo");
                //Liberación de semáforos para cada una de las etapas
                for (int j = 0; j < organizador.length; j++) {
                    organizador[j].release();
                }
                
            } catch (InterruptedException ex) {
                Logger.getLogger(MIPS.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    //Este método carga a partir de txt los programas y llama a cargar a la memoria de instrucciones
    private void cargarArchivos() {
        File[] files = null;
        int palabraActual = 0;
        JFileChooser abridor = new JFileChooser();
        abridor.setMultiSelectionEnabled(true);
        int returnVal = abridor.showOpenDialog(null);
        int iniPrograma = 0;
        int nuevoPC = 0;
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            files = abridor.getSelectedFiles();
            BufferedReader out;
            for (int i = 0; i < files.length; i++) {
                try {
                    String actual = "";
                    out = new BufferedReader(new FileReader(files[i]));
                    nuevoPC = iniPrograma*4;
                    colaCircular.add(new Programa(nuevoPC, new int[32]));//archivos.add("Programa: " + i + 1);
                    while ((actual = out.readLine()) != null) {
                        agregarEnMemoria(actual, palabraActual);
                        palabraActual++;
                        iniPrograma++;
                    }
                } catch (IOException ex) {
                    Logger.getLogger(MIPS.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }
    
    //Guarda cada programa en la memoria de instrucciones
    private void agregarEnMemoria(String actual, int palabraActual) {
        int[] tmp = new int[4];
        StringTokenizer iterador = new StringTokenizer(actual);
        for (int i = 0; i < tmp.length; i++) {
            tmp[i] = Integer.parseInt(iterador.nextElement().toString());
            //   System.out.println(tmp[i]);
        }
        memoriaInstrucciones[palabraActual] = tmp;
    }
    
    private void imprimirMemoria(int[][] tmp) {
        for (int i = 0; i < tmp.length; i++) {
            for (int j = 0; j < tmp[0].length; j++) {
                System.out.print(tmp[i][j] + "|");
            }
            System.out.println();
        }
    }
    
    private void inicializarMemorias() {
        for (int i = 0; i < memoriaDatos.length; i++) {
            for (int j = 0; j < memoriaDatos[0].length; j++) {
                memoriaDatos[i][j] = 1;
            }
        }
    }
    
    private void cargarProgramaActual() {
        ((EtapaIF) etapas[IF]).setPC(colaCircular.get(programaActual).getPC());
        ((EtapaID) etapas[ID]).setRegistros(colaCircular.get(programaActual).getRegistros());
        ((EtapaWB) etapas[WB]).setRegistros(colaCircular.get(programaActual).getRegistros());
    }
    
    private void guardarContexto() {
        colaCircular.get(programaActual).setPC(((zonaCritica[IF].getPC())+4));
        colaCircular.get(programaActual).setRegistros(((EtapaWB) etapas[WB]).getJuegoRegistros());
    }
}
